# Атомарность операции означает, что операция выполняется непрерывно и полностью или не выполняется вообще,
# без возможности частичного выполнения или перекрытия с другими операциями. Атомарные операции являются критически 
# важными для обеспечения корректности и согласованности данных, особенно в многопоточных и асинхронных средах.

# В Python многие операции по умолчанию не атомарны. Например, простые операции с изменяемыми структурами данных, такими
# как списки, словари и множества, не атомарны и могут вызывать состояния гонки или ошибки, если несколько потоков одновременно
# обращаются к этим данным.

# В asyncio атомарность операций может быть обеспечена с помощью асинхронных примитивов синхронизации, например таких как asyncio.Lock, 
# который предоставляет асинхронный механизм блокировки.

# Примеры атомарных операций.
# Давайте рассмотрим несколько примеров. Когда вы работаете с переменными или коллекциями, зачастую операции с ними атомарны.
# my_variable = 10  # Атомарная операция присваивания

# my_list = []      # Создание списка
# my_list.append(5) # Атомарная операция добавления элемента в список

# my_dict = {}      # Создание словаря
# my_dict['key'] = 'value'  # Атомарная операция присваивания значения ключу в словаре

# my_set = set()    # Создание множества
# my_set.add(4)     # Атомарная операция добавления элемента в множество


# my_list[0] = 100  # Атомарная операция изменения элемента списка

# # Проверка условия также считается атомарной операцией
# if my_variable > 5:
#     pass

# # Эти операции являются атомарными в смысле того, что в асинхронной среде они выполняются полностью без прерывания.
# # Это важно для обеспечения надежности и предсказуемости поведения вашего кода.


# # Примеры неатомарных операций.
# # Добавление или вычитание переменной: Это не просто одно действие, а целая серия шагов.
# #a = a + 1  # Это не атомарная операция
# # Здесь происходит три действия: сначала читается значение переменной a, затем вычисляется новое значение (a + 1),
# # и в конце новое значение присваивается переменной a.

# # Доступ и присваивание в списке или словаре: Это тоже не одномоментная операция.
# #a[0] = b[0]  # Это не атомарная операция
# # Тут мы сначала получаем значение из b[0], а потом присваиваем его a[0]. Это два отдельных шага.

# #Инкрементирование элемента списка:
# # ​​​​​​#​my_list = [0, 1, 2]
# # my_list[0] += 1  # Это не атомарная операция
# # Здесь сначала происходит чтение значения my_list[0], затем к нему прибавляется 1, и после этого новое значение присваивается my_list[0].

# # Обновление значения в словаре:

# my_dict = {'count': 0}
# my_dict['count'] += 1  # Это не атомарная операция
# # Такой процесс также включает в себя несколько шагов: сначала извлекается значение my_dict['count'], к нему прибавляется 1,
# # а затем новое значение записывается обратно в словарь.


# #Конкатенация строк:

# my_string = "Hello"
# my_string += " World"  # Это не атомарная операция
# # При конкатенации строк сначала создается новая строка из "Hello" и " World", а затем новая строка присваивается переменной my_string.

# # Удаление элемента из списка:

# items = ['apple', 'banana', 'cherry']
# del items[1]  # Это не атомарная операция
# Удаление элемента из списка включает в себя изменение размера списка и перенос оставшихся элементов, что представляет собой серию операций.

#Модификация содержимого файла:

# with open('file.txt', 'r+') as file:
#     content = file.read()
#     content += 'new data'
#     file.seek(0)
#     file.write(content)  # Это не атомарная операция
# Чтение и последующая запись в файл также являются неатомарными, поскольку они включают в себя несколько шагов и могут быть прерваны.


# Аналогия из реальной жизни с атомарностью:
# Атомарность в реальной жизни можно сравнить с процессом обслуживания в банке, когда вам необходимо получить доступ к своему банковскому
# счету для совершения операций, снятие наличных или перевод средств.

# Представьте, что вы пришли в банк и обратились к операционисту для снятия денег со своего счета. В это же время ваш член семьи также пришел
# в банк и хочет перевести деньги на ваш счет. Если банк обрабатывает эти две операции одновременно без какого-либо порядка или контроля,
# это может привести к неправильным результатам, таким как некорректное изменение баланса на вашем счете.


# Банк должен гарантировать, что только одна операция может быть обработана в данный момент времени. Например, сначала обрабатывается снятие
# наличных, а затем перевод средств. Таким образом, предотвращаются возможные ошибки и сохраняется целостность данных.

# Код ниже демонстрирует проблему, связанную с недостатком атомарности при обновлении общего ресурса в асинхронном контексте.
# import asyncio

# # Общий ресурс, который будет обновляться
# shared_resource = 0


# async def update_resource():

#     # Используем глобальную переменную shared_resource
#     global shared_resource
#     print('Начинаем обновление shared_resource')

#     # Сохраняем текущее значение shared_resource во временную переменную
#     temp = shared_resource

#     # Имитация операции ввода-вывода
#     await asyncio.sleep(1)  

#     # Увеличиваем значение shared_resource на 1
#     shared_resource = temp + 1
#     print('Обновление shared_resource завершено')


# async def main():
#     await asyncio.gather(update_resource(), update_resource())
#     print(f'shared_resource: {shared_resource}')

# asyncio.run(main())

# Вывод:

# Начинаем обновление shared_resource
# Начинаем обновление shared_resource
# Обновление shared_resource завершено
# Обновление shared_resource завершено
# shared_resource: 1

# В коде есть глобальная переменная shared_resource, которая представляет собой общий ресурс.
# Этот ресурс обновляется в асинхронной функции update_resource(). В этой функции текущее значение
# shared_resource сохраняется во временную переменную temp, после чего происходит имитация операции ввода-вывода с использованием 
# asyncio.sleep(1). Затем значение shared_resource увеличивается на 1.

# В главной асинхронной функции main() запускаются две асинхронные задачи для обновления shared_resource.
# Однако из-за отсутствия атомарности эти две задачи могут привести к непредсказуемым результатам. Например,
# если первая задача прочитает значение shared_resource, затем уступит управление второй задаче
# (которая также прочитает значение shared_resource), обе задачи увидят одно и то же исходное значение и увеличат его на 1.

# В результате shared_resource увеличится только на 1, а не на 2, как можно было бы ожидать.
# Это является классическим примером проблемы состояния гонки (race condition), когда результат
# выполнения кода зависит от неопределённого или неконтролируемого порядка выполнения операций, которые
# изменяют общие данные. Причем порядок, в котором эти операции выполняются, влияет на результат, который 
# становится труднопредсказуемым.

# Для обеспечения безопасности/атомарности при обновлении общего ресурса используем асинхронный замок asyncio.Lock(). 

#  Замок lock используется для обеспечения того, чтобы только одна корутина могла обновлять shared_resource в любой момент времени,
# предотвращая таким образом возникновение условий гонки.
import asyncio

# Общий ресурс, который будет обновляться
shared_resource = 0

# Создаем асинхронный замок для обеспечения безопасности при обновлении shared_resource
lock = asyncio.Lock()


async def update_resource():

    # Используем глобальную переменную shared_resource
    global shared_resource
    print('Начинаем обновление shared_resource')

    # Используем асинхронный замок для обеспечения безопасности при обновлении shared_resource
    async with lock:

        # Сохраняем текущее значение shared_resource во временную переменную
        temp = shared_resource
        await asyncio.sleep(1)

        # Увеличиваем значение shared_resource на 1
        shared_resource = temp + 1
    print('Обновление shared_resource завершено')

async def main():
    await asyncio.gather(update_resource(), update_resource())
    print(f'shared_resource: {shared_resource}')

asyncio.run(main())
# Теперь каждая операция обновления ресурса выполняется атомарно. Перед выполнением операции блокируется замок asyncio.Lock(),
# и другие корутины не могут получить доступ к общему ресурсу, пока замок не будет разблокирован. Это гарантирует, что каждое
# обновление будет выполнено полностью, прежде чем начнется следующее обновление.

# В данном коде операция обновления ресурса состоит из трех шагов:

# Чтение текущего значения,
# Задержка (имитация операции ввода-вывода),
# Запись обновленного значения.

#Эти шаги могут быть прерваны, что может привести к проблемам согласованности данных, если не использовать механизмы синхронизации, такие как замки asyncio.Lock().